package com.yscp.ysdemo.common;

import org.apache.commons.lang3.CharUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.io.*;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 敏感词过滤器，字典树实现
 */
@Component
public class WordsFilter {
    private static final String REPLACEWORD = "*";      //要替换敏感词汇的字符
    private static TrieNode rootNode;//根节点

    public WordsFilter(){
    }
//    如果项目中bean是通过注解方式管理的，需要在初始化完成后，执行指定方法，仅仅需要在需要执行的方法上添加@PostConstruct注解即可，修改后的JAVA类如下：
    @PostConstruct
    public void init(){
        BufferedReader reader = null;
        try {
            ClassPathResource classPathResource = new ClassPathResource("static/sensitive-words.txt");
            InputStream is = classPathResource.getInputStream();
            //一定要留意编码问题，统一编码
             reader = new BufferedReader(new InputStreamReader(is,"UTF-8"));
             String keyWord;
             rootNode = new TrieNode();
             while((keyWord = reader.readLine())!=null){
                 this.addKeyWord(keyWord);
             }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

    }
    //将敏感词添加到前缀树
    private void addKeyWord(String keyWord) {
        TrieNode tempNode = rootNode;
        for (int i = 0; i < keyWord.length(); i++) {
            char c = keyWord.charAt(i);
            TrieNode subNode = tempNode.getSubNode(c);
            //如果该节点之前没有就要新加上
            if (subNode == null){
                subNode = new TrieNode();
                tempNode.addSubNode(c,subNode);
            }
            tempNode = subNode;
            if(i==keyWord.length() - 1)
                tempNode.setKeyWordEnd(true);   //设置敏感词结束标识
        }

    }

    /**
     *
     * @param text 待检测字符串
     * @return 内容是否合法，true为合法
     */
    public boolean checkWord(String text){
        boolean isOk = true;
        if (text == null || "".equals(text))
            return isOk;
//        if (rootNode == null){  //初始化前缀树其实还是应该放在服务器启动时
//            init();
//        }
        TrieNode tempNode = rootNode;
        int begin = 0;
        int pos = 0;
        boolean flag = false;
        List<Integer> wordPos = new ArrayList<>();  //存储要处理文本的下标位置方便后面进行替换
        StringBuilder sb = new StringBuilder(text); //存储原内容，之后直接一个个替换成*即可
        //对于富文本的标签还是比较难一次性处理，这里就先遍历一次将需要过滤的文本内容对应的位置找到放入集合方便之后过滤
        // 过滤掉标签和特殊字符再进行敏感词过滤
        for (int index = 0; index < text.length(); index++) {
            char c = text.charAt(index);
            //带尖括号的是标签。这次不用考虑处理双引号里面的那些尖括号了，前面过滤的时候已经把不是标签的尖括号转义了（jsoup确实强大）
            if(!flag){
                if (c == '<' && !flag){
                    flag=true;
                    continue;
                }
                //如果不是普通字符或者，（c < 0x2E80 || c >0x9FFF这个是东亚文字，这个不能被当成特殊符号处理，所以这里要排查掉），直接不进行筛选
                if (!CharUtils.isAsciiAlphanumeric(c)&& (c < 0x2E80 || c >0x9FFF)){
                    continue;
                }
                wordPos.add(index);
            }else{
                if (c == '>' && flag){
                    flag = false;
                    continue;
                }
            }
        }
        while (pos < wordPos.size()){
            char c = text.charAt(wordPos.get(pos));
            tempNode = tempNode.getSubNode(c);
            if (tempNode == null){
                //以begin开头的字符串不是敏感词
                pos = ++begin;
                tempNode = rootNode;
            }else if (tempNode.isKeyWordEnd()){
                isOk = false;
                break;
            }else{
                pos++;
            }
        }
        return isOk;
    }


    /**
     *
     * @param text 要被过滤的字符串
     * @return  过滤完毕的字符串
     */
    public String filterWord(String text){  //处理不了如赌s博s这种字符，因为's'也是被当做实体字符处理的，如果限定敏感词为中文，可以设置英文也为特殊字符跳过
        if (text == null || "".equals(text))
            return null;
        if (rootNode == null){  //初始化前缀树其实还是应该放在服务器启动时
            init();
        }
        TrieNode tempNode = rootNode;
        int begin = 0;
        int pos = 0;
        boolean flag = false;
        List<Integer> wordPos = new ArrayList<>();  //存储要处理文本的下标位置方便后面进行替换
        StringBuilder sb = new StringBuilder(text); //存储原内容，之后直接一个个替换成*即可
        //对于富文本的标签还是比较难一次性处理，这里就先遍历一次将需要过滤的文本内容对应的位置找到放入集合方便之后过滤
        // 过滤掉标签和特殊字符再进行敏感词过滤
        for (int index = 0; index < text.length(); index++) {
            char c = text.charAt(index);
            //带尖括号的是标签。这次不用考虑处理双引号里面的那些尖括号了，前面过滤的时候已经把不是标签的尖括号转义了（jsoup确实强大）
            if(!flag){
                if (c == '<' && !flag){
                    flag=true;
                    continue;
                }
                //如果不是普通字符或者，（c < 0x2E80 || c >0x9FFF这个是东亚文字，这个不能被当成特殊符号处理，所以这里要排查掉），直接不进行筛选
                if (!CharUtils.isAsciiAlphanumeric(c)&& (c < 0x2E80 || c >0x9FFF)){
                    continue;
                }
                wordPos.add(index);
            }else{
                if (c == '>' && flag){
                    flag = false;
                    continue;
                }
            }
        }
        while (pos < wordPos.size()){
            char c = text.charAt(wordPos.get(pos));
                tempNode = tempNode.getSubNode(c);
                if (tempNode == null){
                    //以begin开头的字符串不是敏感词
                    pos = ++begin;
                    tempNode = rootNode;
                }else if (tempNode.isKeyWordEnd()){
                    for (int i = begin;i <=pos; i++) {
                        //replace替换第一个参数位置到第二个参数减一的位置的字符串，所以这里替换单个字符要加一
                        sb.replace(wordPos.get(i),wordPos.get(i)+1,REPLACEWORD);
                    }
                    begin = ++pos;
                    tempNode = rootNode;
                }else{
                    pos++;
                }
        }
        return sb.toString();
//        while (pos < text.length()){
//            char c = text.charAt(pos);
//            //跳过符号
//            if(isSymol(c)){     //??????
////                if(tempNode == rootNode){
////                    sb.append(c);
////                    begin++;
////                }
////                pos++;
////                continue;
//                if(begin == pos){
//                    begin = ++pos;
//                }else
//                    pos++;
//                continue;
//            }
//
//            tempNode = tempNode.getSubNode(c);
//            if (tempNode == null){
//                //以begin开头的字符串不是敏感词
//                sb.append(text.charAt(begin));
//                pos = ++begin;
//                tempNode = rootNode;
//            }else if (tempNode.isKeyWordEnd()){
//                sb.append(REPLACEWORD);
//                begin = ++pos;
//                tempNode = rootNode;
//            }else{
//                pos++;
//            }
//        }
//        sb.append(text.substring(begin));

    }

//    private boolean isSymol(Character c){
//        return true;
//    }
    //前缀树
    private class TrieNode{
        //结束标识
        private boolean isKeyWordEnd = false;
        //节点，当前节点内容（字符）和指向下一个节点的指针
        private Map<Character,TrieNode> subNodes = new HashMap<>();
        public boolean isKeyWordEnd() {
            return isKeyWordEnd;
        }

        public void setKeyWordEnd(boolean keyWordEnd) {
            isKeyWordEnd = keyWordEnd;
        }
        //添加子节点
        public void addSubNode(Character c,TrieNode node){
            subNodes.put(c,node);
        }
//        得到对应子节点
        public TrieNode getSubNode(Character c){
            return subNodes.get(c);
        }
    }

//    public static void main(String[] args) {
//        WordsFilter wordsFilter = new WordsFilter();
//        String str= "<p>操<br></p>";
//        System.out.println(wordsFilter.filterWord(str));

//    }
}
